package com.xqboss.chain.service;

import com.alibaba.fastjson2.JSONObject;
import com.xqboss.chain.config.EChainConfig;
import com.xqboss.chain.domain.chain.UpUpdateChainLog;
import com.xqboss.chain.domain.chain.dto.EChainTxRetDto;
import com.xqboss.chain.domain.chain.vo.CallBackResult;
import com.xqboss.chain.echain.EChainSDK;
import com.xqboss.chain.echain.HttpRequest;
import com.xqboss.chain.echain.Util;
import com.xqboss.chain.enums.UpChainStatusEnum;
import com.xqboss.common.core.redis.RedisCache;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.uuid.IdUtils;
import lombok.extern.slf4j.Slf4j;
import org.fisco.bcos.sdk.jni.utilities.tx.TxPair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 亿链服务
 * </p>
 *
 * @author Trent
 * @date 2023/8/14
 */
@Service
@Slf4j
public class EChainService {

    @Autowired
    private EChainConfig eChainConfig;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private UpUpdateChainLogService upUpdateChainLogService;

    private EChainSDK sdk = new EChainSDK();


    /**
     * 新建合约
     *
     * @return
     */
    public String createContract(){
        String address = eChainConfig.getAddress();
        String reqNo = Util.formatDeployPayload(IdUtils.fastSimpleUUID(), address);
        return deployContract(reqNo, address);
    }

    public EChainTxRetDto safeMint(String contract, String toAddress, BigInteger tokenId, String ddcUri, byte[] data) throws Exception {
        Integer blockNumber = getCacheBlockNumber();
        if (Objects.isNull(blockNumber)) {
            // 必须要有区块号才能发起请求
            throw new ServiceException("获取区块号失败！");
        }
        TxPair txPair = sdk.signMint(toAddress, tokenId, contract, eChainConfig.getPrivateKey(), blockNumber);
        String response = sendTx(txPair, eChainConfig.getCallbackUrl());
        EChainTxRetDto result = new EChainTxRetDto();
        result.setHash(txPair.getTxHash());
        result.setReqNo(txPair.getTxHash());
        result.setResponse(response);
        result.setSignTxInfo(txPair.getSignedTx());
        return result;
    }

    public EChainTxRetDto safeTransfer(String contract, String fromPrivate, String fromAddress, String toAddress, BigInteger tokenId, byte[] data) throws Exception {
        Integer blockNumber = getCacheBlockNumber();
        if (Objects.isNull(blockNumber)) {
            // 必须要有区块号才能发起请求
            throw new ServiceException("获取区块号失败！");
        }
        TxPair txPair = sdk.signTransferFrom(fromAddress, toAddress, tokenId, contract, fromPrivate, blockNumber);
        String response = sendTx(txPair, eChainConfig.getCallbackUrl());

        EChainTxRetDto result = new EChainTxRetDto();
        result.setHash(txPair.getTxHash());
        result.setReqNo(txPair.getTxHash());
        result.setResponse(response);
        result.setSignTxInfo(txPair.getSignedTx());
        return result;
    }


    public EChainTxRetDto burn(String contract, String fromPrivate, BigInteger tokenId) throws Exception {
        Integer blockNumber = getCacheBlockNumber();
        if (Objects.isNull(blockNumber)) {
            // 必须要有区块号才能发起请求
            throw new ServiceException("获取区块号失败！");
        }
        TxPair txPair = sdk.signBurn(tokenId, contract, fromPrivate, blockNumber);
        String response = sendTx(txPair, eChainConfig.getCallbackUrl());

        EChainTxRetDto result = new EChainTxRetDto();
        result.setHash(txPair.getTxHash());
        result.setReqNo(txPair.getTxHash());
        result.setResponse(response);
        result.setSignTxInfo(txPair.getSignedTx());
        return result;
    }

    public void syncTransfer(String txHash) {
        String payload = Util.formatQueryPayload("getTransactionReceipt", Arrays.asList(txHash, false));
        String response = HttpRequest.sendPost(eChainConfig.getBaseUrl() + "/chain/rpc/query", payload, eChainConfig);
        JSONObject json = JSONObject.parseObject(response);
        if("EC000000".equals(json.getString("code"))){
            // 查询成功

        }else{
            throw new ServiceException("同步失败");
        }
    }

    /**
     * 上链
     *
     * @param txPair      交易信息
     * @param callbackUrl 回调地址
     * @return
     * @throws Exception
     */
    private String sendTx(TxPair txPair, String callbackUrl) throws Exception {
        String payload = Util.formatSendTxPayload(txPair.getTxHash(), txPair.getSignedTx(), callbackUrl);
        String response = HttpRequest.sendPost(eChainConfig.getBaseUrl() + "/chain/rpc/tx", payload, eChainConfig);
        return response;
    }

    /**
     * 获取区块交易号
     *
     * @return
     */
    private Integer getCacheBlockNumber() {
        //首先从缓存中获取区块号
        String cacheBlockNumberKey = "cache-block-number";
        Integer blockNumber = redisCache.getCacheObject(cacheBlockNumberKey);
        if (Objects.isNull(blockNumber)) {
            //从链上获取最新的区块
            try {
                blockNumber = getBlockNumber();
                redisCache.setCacheObject(cacheBlockNumberKey, blockNumber, 10, TimeUnit.SECONDS);
            } catch (Exception e) {
                //失败了睡眠一会儿再请求一次
                e.printStackTrace();
                try {
                    Thread.sleep(1000);
                    blockNumber = getBlockNumber();
                    redisCache.setCacheObject(cacheBlockNumberKey, blockNumber, 10, TimeUnit.SECONDS);
                } catch (Exception interruptedException) {
                }
            }
        }
        //区块号
        return blockNumber;
    }

    /**
     * 获取区块号
     *
     * @return
     * @throws Exception
     */
    private int getBlockNumber() throws Exception {
        String payload = Util.formatQueryPayload("getBlockNumber", new ArrayList<>());
        String response = HttpRequest.sendPost(eChainConfig.getBaseUrl() + "/chain/rpc/query", payload, eChainConfig);
        //{"code":"EC000000","message":"成功","data":{"id":105457,"jsonrpc":"2.0","result":"731434","error":null,"rawResponse":null,"blockNumber":731434}}
        System.out.println(response);
        JSONObject obj = JSONObject.parseObject(response);
        if (!obj.getString("code").equals("EC000000")) {
            throw new Exception("请求区块号失败：" + obj.getString("message"));
        } else {
            return obj.getJSONObject("data").getInteger("blockNumber");
        }
    }

    /**
     * 部署合约
     *
     * @param reqNo
     * @param owner
     * @return
     * @throws Exception
     */
    public String deployContract(String reqNo, String owner) {
        String payload = Util.formatDeployPayload(reqNo, owner);
        String response = HttpRequest.sendPost(eChainConfig.getBaseUrl() + "/chain/contract/deploy", payload, eChainConfig);
        JSONObject obj = JSONObject.parseObject(response);
        if (!obj.getString("code").equals("EC000000")) {
            throw new ServiceException("请求合约部署失败：" + obj.getString("message"));
        } else {
            return obj.getJSONObject("data").getString("contractAddress");
        }
    }


    /**
     * 上链通知回调
     *
     * @param request
     * @return
     */
    public CallBackResult callback(String request) {
        CallBackResult callBackResult = new CallBackResult();
        callBackResult.setCode("200");
        callBackResult.setMsg("success");
        log.info(request);
        JSONObject obj = JSONObject.parseObject(request);
        if ("EC000000".equals(obj.getString("code"))) {
            String reqNo = obj.getJSONObject("data").getString("reqNo");
            // 根据请求编号获取请求记录
            UpUpdateChainLog upChainLog = upUpdateChainLogService.getByReqNo(reqNo);
            if (Objects.isNull(upChainLog)) {
                // 交易不存在！
                throw new ServiceException("交易不存在！");
            }
            if (upChainLog.getTxStatus() == UpChainStatusEnum.NORMAL || upChainLog.getTxStatus() == UpChainStatusEnum.PENDING) {
                JSONObject rpcResp = obj.getJSONObject("data").getJSONObject("jsonRpcResp");
                JSONObject result = rpcResp.getJSONObject("result");
                if (Objects.isNull(result)) {
                    // result为null,表示交易未上链
                    upUpdateChainLogService.updateStatus(upChainLog.getId(), UpChainStatusEnum.FAILED, request);
                } else {
                    //- status 为 0，则代表上链成功，判断包体其中 status 为 0，或 statusOK 为 true；
                    //- status 为 16 是在出块过程中执行合约逻辑失败交易回滚，会将具体错误响应;
                    //- status 为 19 的如第五节描述，请求合约地址不存在，更改已部署的合约地址；
                    Integer status = result.getInteger("status");
                    if (status.equals(0)) {
                        upUpdateChainLogService.updateStatus(upChainLog.getId(), UpChainStatusEnum.SUCCESS, request);
                    } else if (status.equals(16)) {
                        upUpdateChainLogService.updateStatus(upChainLog.getId(), UpChainStatusEnum.FAILED, request);
                    } else if (status.equals(19)) {
                        upUpdateChainLogService.updateStatus(upChainLog.getId(), UpChainStatusEnum.FAILED, request);
                    } else {
                        upUpdateChainLogService.updateStatus(upChainLog.getId(), UpChainStatusEnum.FAILED, request);
                    }
                }
            }
        }
        return callBackResult;
    }


}
